#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include <common/macro/build.h>
#include <common/macro/array.h>
#include <common/macro/debug.h>

#include <storage/tuple/tuple.h>

API_LIBLOCAL tuple_t *tuple_new() {
	tuple_t *tuple;

	tuple = malloc(sizeof(*tuple));
	tuple_init(tuple);

	return tuple;
}

API_LIBLOCAL void tuple_init(tuple_t *tuple) {
	tuple->offset = 0;

	tuple->atoms = NULL;
	tuple->atoms_c = 0;

	tuple->deleted = TUPLE_EXISTING;
	tuple->id_origin = 0;
	tuple->id_insert = 0;
	tuple->id_delete = 0;
	tuple->id_command = 0;
}

API_LIBLOCAL tuple_delete_type_e tuple_delete_char_to_type(const char c) {
	switch (c) {
		case 'D':
			return TUPLE_DELETE;

		case 'U':
			return TUPLE_UPDATE;

		default:
			break;
	}

	return TUPLE_EXISTING;
}

API_LIBLOCAL char tuple_delete_type_to_char(const tuple_delete_type_e type) {
	switch (type) {
		case TUPLE_DELETE:
			return 'D';

		case TUPLE_UPDATE:
			return 'U';

		default:
			break;
	}

	return '\0';
}

API_LIBLOCAL tuple_atom_type_e tuple_atom_char_to_type(const char c) {
	switch (c) {
		case 'D':
			return TUPLE_ATOM_DATA;

		case 'B':
			return TUPLE_ATOM_BLOBLINK;
	}

	return TUPLE_ATOM_DATA;
}

API_LIBLOCAL char tuple_atom_type_to_char(const tuple_atom_type_e type) {
	switch (type) {
		case TUPLE_ATOM_DATA:
			return 'D';

		case TUPLE_ATOM_BLOBLINK:
			return 'B';
	}

	return 'D';
}

API_LIBLOCAL inline size_t tuple_headersize(tuple_t *tuple) {
	return sizeof(tuple->id_insert) +
	   sizeof(tuple->id_delete) +
	   sizeof(tuple->id_origin) +
	   sizeof(tuple->id_command) +
	   sizeof(tuple->offset) +
	   sizeof(char);
}

API_LIBLOCAL inline size_t tuple_datasize(tuple_t *tuple) {
	size_t rv;
	tuple_atoms_count_t i;

	rv = 0;
	rv += sizeof(tuple->atoms_c);
	for (i = 0; i < tuple->atoms_c; i++) {
		rv += sizeof(char);
		rv += sizeof(tuple->atoms[i].size);
		rv += tuple->atoms[i].size;
	}
	
	return rv;
}

API_LIBLOCAL int tuple_serialize(tuple_t *tuple, char *out, ssize_t out_size) {
	char atom_type_char;
	char is_deleted;
	ssize_t offset, data_offset, itemlen;
	tuple_atoms_count_t i;

	assert(tuple != NULL);
	assert(out != NULL);

	offset = 0;
	
	/* Write the metadata. */
	debugmsg("Serializing tuple %p to output %p (%u).\n", tuple, out, out_size);

	/* Tuple insert transaction ID. */
	itemlen = sizeof(tuple->id_insert);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &tuple->id_insert, itemlen);
	offset += itemlen;

	/* Tuple delete transaction ID. */
	itemlen = sizeof(tuple->id_delete);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &tuple->id_delete, itemlen);
	offset += itemlen;

	/* Tuple origin transaction ID. */
	itemlen = sizeof(tuple->id_origin);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &tuple->id_origin, itemlen);
	offset += itemlen;

	/* Tuple insert command ID. */
	itemlen = sizeof(tuple->id_command);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &tuple->id_command, itemlen);
	offset += itemlen;

	/* Tuple data offset. */
	itemlen = sizeof(tuple->offset);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &tuple->offset, itemlen);
	offset += itemlen;

	/* Tuple deleted?. */
	is_deleted = tuple_delete_type_to_char(tuple->deleted);
	itemlen = sizeof(is_deleted);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &is_deleted, itemlen);
	offset += itemlen;
	
	/* tuple->offset refers to offset form the end of the data area. */
	data_offset = tuple->offset;

	/* Atoms count. */
	itemlen = sizeof(tuple->atoms_c);
	data_offset += itemlen;
	if (out_size - data_offset < offset) { return -1; }
	memcpy(&out[out_size - data_offset], &tuple->atoms_c, itemlen);

	/* Write the actual data. */
	for (i = 0; i < tuple->atoms_c; i++) {
		/* Atom type. */
		itemlen = sizeof(char);
		data_offset += itemlen;
		if (out_size - data_offset < offset) { return -1; }
		atom_type_char = tuple_atom_type_to_char(tuple->atoms[i].type);
		memcpy(&out[out_size - data_offset], &atom_type_char, itemlen);

		/* Atom size. */
		itemlen = sizeof(tuple->atoms[i].size);
		data_offset += itemlen;
		if (out_size - data_offset < offset) { return -1; }
		memcpy(&out[out_size - data_offset], &tuple->atoms[i].size, itemlen);
			
		/* Atom data. */
		itemlen = tuple->atoms[i].size;
		data_offset += itemlen;
		if (out_size - data_offset < offset) { return -1; }
		memcpy(&out[out_size - data_offset], tuple->atoms[i].data, itemlen);
	}

	return 0;
}

API_LIBLOCAL int tuple_deserialize(tuple_t **tuple, char *in, ssize_t in_size) {
	char atom_type_char;
	char is_deleted;
	ssize_t offset, data_offset, itemlen;
	tuple_atom_t atom;
	tuple_atoms_count_t i;

	assert(tuple != NULL);
	assert(in != NULL);

	offset = 0;

	if (*tuple == NULL)
		*tuple = tuple_new();

	/* Tuple insert transaction ID. */
	itemlen = sizeof((*tuple)->id_insert);
	if (itemlen > in_size - offset) { tuple_destroy(*tuple); return -1; }
	memcpy(&(*tuple)->id_insert, &in[offset], itemlen);
	offset += itemlen;

	/* Tuple delete transaction ID. */
	itemlen = sizeof((*tuple)->id_delete);
	if (itemlen > in_size - offset) { tuple_destroy(*tuple); return -1; }
	memcpy(&(*tuple)->id_delete, &in[offset], itemlen);
	offset += itemlen;

	/* Tuple origin transaction ID. */
	itemlen = sizeof((*tuple)->id_origin);
	if (itemlen > in_size - offset) { tuple_destroy(*tuple); return -1; }
	memcpy(&(*tuple)->id_origin, &in[offset], itemlen);
	offset += itemlen;

	/* Tuple insert command ID. */
	itemlen = sizeof((*tuple)->id_command);
	if (itemlen > in_size - offset) { tuple_destroy(*tuple); return -1; }
	memcpy(&(*tuple)->id_command, &in[offset], itemlen);
	offset += itemlen;

	/* Tuple offset. */
	itemlen = sizeof((*tuple)->offset);
	if (itemlen > in_size - offset) { tuple_destroy(*tuple); return -1; }
	memcpy(&(*tuple)->offset, &in[offset], itemlen);
	offset += itemlen;

	/* Tuple deleted?. */
	itemlen = sizeof(is_deleted);
	if (itemlen > in_size - offset) { tuple_destroy(*tuple); return -1; }
	memcpy(&is_deleted, &in[offset], itemlen);
	offset += itemlen;

	(*tuple)->deleted = tuple_delete_char_to_type(is_deleted);
	
	/* tuple->offset refers to offset form the end of the data area. */
	data_offset = (*tuple)->offset;

	/* Atoms count. */
	itemlen = sizeof((*tuple)->atoms_c);
	data_offset += itemlen;
	if (in_size - data_offset < offset) { return -1; }
	memcpy(&(*tuple)->atoms_c, &in[in_size - data_offset], itemlen);

	/* Allocate. */
	ARRAY_ALLOC((*tuple)->atoms, (*tuple)->atoms_c);

	/* Read the actual data. */
	for (i = 0; i < (*tuple)->atoms_c; i++) {
		/* Atom type. */
		itemlen = sizeof(char);
		data_offset += itemlen;
		if (in_size - data_offset < offset) {
			debugmsg("MEGADETH!!!!\n");
			tuple_destroy(*tuple);
			return -1;
		}
		memcpy(&atom_type_char, &in[in_size - data_offset], itemlen);
		atom.type = tuple_atom_char_to_type(atom_type_char);

		/* Atom size. */
		itemlen = sizeof(atom.size);
		data_offset += itemlen;
		if (in_size - data_offset < offset) {
			tuple_destroy(*tuple);
			return -1;
		}
		memcpy(&atom.size, &in[in_size - data_offset], itemlen);
			
		/* Atom data. */
		itemlen = atom.size;
		data_offset += itemlen;
		atom.data = &in[in_size - data_offset];

		/* Data read, append it. */
		ARRAY_SET((*tuple)->atoms, i, atom);
	}

	return 0;
}

API_LIBLOCAL void tuple_deinit(tuple_t *tuple) {
	if (tuple == NULL) return;

	free(tuple->atoms);
	tuple->atoms_c = 0;
	tuple->atoms = NULL;
}

API_LIBLOCAL void tuple_destroy(tuple_t *tuple) {
	if (tuple == NULL) return;

	free(tuple->atoms);
	free(tuple);
}
